#include <stdio.h>
#include "control.h"
#include "alu.h"
#include "io.h"
#include "memory.h"
#include "type.h"
#include "version.h"

#undef DEBUG

struct gax_header 
{
	word version;
	word data_words;
	word text_words;
};

void load (void);

int main (int argc, char **argv)
{
	word w = 0;
	char buf [64];
	word r, s;
	int i;

	word pc;
	word nextpc;
	word instruction;
	word opcode, creg, areg, breg, im22, im18;
	word cval, aval, bval;
	word aluop;
	word aludata;
	word aarg, barg;
	word iodata, ioport;
	word memdata, memaddr;
	word regdata;

	if (argc > 1)
	{
		close (0);
		open (argv [1], O_RDONLY);
	}
	
	load (); // get the program into memory
	set_register (SP, MEMORY_WORDS - 1); // put the stack pointer at the top of memory

	start_device (KBD, 0);
	start_device (TTY, 0);
	w = (word) time (0); // seed the prng
	start_device (RND, &w);
	do
	{
		pc = get_pc ();
		instruction = fetch (pc);
		decode (instruction, &opcode, &creg, &areg, &breg, &im22, &im18);
#ifdef DEBUG
		sprintf (buf, "\n%08x %d %d %d %d %d %d \n", instruction, opcode, creg, areg, breg, im22, im18);
		tty_puts (buf);
#endif
		set_flags (opcode);

		aval = get_register (areg); // read from the registers 
		bval = get_register (breg);
		cval = get_register (creg);

		if (SET == getPcSrc () && 0 != cval) // we are jumping
		{
			if (SET == getPcReg ()) // to the address in the a register
			{
				nextpc = aval;
			}
			else // to the address in the 22 bit immediate
			{
				nextpc = im22;
			}
		}
		else 
		{
			nextpc = pc + 1;
		}
		set_pc (nextpc); // now we know the next instruction
		
		aarg = aval;
		if (SET == getAluSrc ()) // alu second argument comes from a register
		{
			barg = bval;
		}
		else // alu second argument comes from the 18 bit immediate value
		{
			barg = im18;
		}
		aluop = getAluOp ();
		aludata = alu (aarg, barg, aluop); // alu operation

		ioport = im22;
		iodata = cval;
		if (SET == getPortWrite ()) // we're doing output
		{
			write_port (ioport, iodata);
		}
		else if (SET == getPortRead ()) // we're getting input
		{
			iodata = read_port (ioport);
		}

		if (SET == getMemAddr ()) // the memory address is from a register
		{
			memaddr = aval;
		}
		else
		{
			memaddr = im22;
		}
		memdata = cval;
		if (SET == getMemWrite ()) // we're storing
		{
			write_memory (memaddr, memdata);
		}
		else
		{
			memdata = read_memory (memaddr);
		}
		
		if (SET == getRegWrite ()) // we're loading
		{
			if (SET == getRegData ()) // loading from memory
			{
				regdata = memdata;
			}
			else // from alu or io bus
			{
				regdata = aludata;
			}

			if (SET == getPortRead ()) // loading from io bus
			{
				regdata = iodata;
			}
			
			set_register (creg, regdata);
		}
#ifdef DEBUG
		dump_registers ();
#endif
		
	}
	while (opcode != HLT);
	stop_device (KBD);
	stop_device (TTY);
	stop_device (RND);
	return 0;
}

void load (void)
{
	word buf [1024];
	struct gax_header h;
	int c;
	int bytes_read = 0;
	int this_chunk;
	read (0, &h, sizeof (struct gax_header));
	if (h.version != GAX_VERSION)
	{
		fprintf (stderr, "Unsupported version %d. Need version %d. \n", h.version, GAX_VERSION);
		exit (1);
	}
	do
	{
		if (h.data_words - bytes_read > 1024)
		{
			this_chunk = 1024;
		}
		else
		{
			this_chunk = h.data_words - bytes_read;
		}
		bytes_read += this_chunk;
		c = read (0, buf, this_chunk * sizeof (word));
		load_memory (buf, c);
	}
	while (bytes_read < h.data_words);
	bytes_read = 0;
	do
	{
		if (h.text_words - bytes_read > 1024)
		{
			this_chunk = 1024;
		}
		else
		{
			this_chunk = h.text_words - bytes_read;
		}
		bytes_read += this_chunk;
		c = read (0, buf, this_chunk * sizeof (word));
		load_instruction (buf, c);
	}
	while (bytes_read < h.text_words);
}

